package com.getpebble.android.framework.developer;

import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import com.getpebble.android.common.auth.PblSessionManager;
import com.getpebble.android.common.core.trace.Trace;
import com.getpebble.android.common.model.FrameworkState;
import com.getpebble.android.config.BootConfig;
import com.getpebble.android.framework.DeviceInterfaces.IDeviceMessageSender;
import com.getpebble.android.framework.developer.DeveloperConnectionManager.WebSocketCommand;
import com.getpebble.android.framework.util.ByteUtils;
import com.koushikdutta.async.ByteBufferList;
import com.koushikdutta.async.DataEmitter;
import com.koushikdutta.async.callback.CompletedCallback;
import com.koushikdutta.async.callback.DataCallback;
import com.koushikdutta.async.http.AsyncHttpClient;
import com.koushikdutta.async.http.AsyncHttpClient.WebSocketConnectCallback;
import com.koushikdutta.async.http.WebSocket;
import com.koushikdutta.async.http.WebSocket.StringCallback;
import java.lang.ref.WeakReference;
import java.net.URI;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;

public class DeveloperConnectionClient implements IConnection, CompletedCallback, DataCallback, StringCallback {
    public static final String TAG = DeveloperConnectionClient.class.getSimpleName();
    private static DeveloperConnectionClient sInstance = null;
    private Handler mCallbackHandler;
    private WeakReference<Context> mContext;
    private FrameworkState mFrameworkState;
    private DeveloperConnectionManager mManager;
    private IDeviceMessageSender mMessageSender;
    private PblSessionManager mSessionManager;
    private Handler mTimeoutHandler = new Handler(Looper.getMainLooper());
    private Runnable mTimeoutRunnable = new Runnable() {
        public void run() {
            Trace.debug(DeveloperConnectionClient.TAG, "Connection timeout");
            DeveloperConnectionClient.this.closeConnection();
        }
    };
    private boolean mWaitingForHandshake = false;
    private WebSocket mWebSocket;

    public static void connectToServer(final Context context, final FrameworkState frameworkState, final IDeviceMessageSender messageSender) {
        DeveloperConnectionManager.getHandler().post(new Runnable() {
            public void run() {
                DeveloperConnectionClient.connectToServerInternal(context, frameworkState, messageSender);
            }
        });
    }

    private static synchronized void connectToServerInternal(Context context, FrameworkState frameworkState, IDeviceMessageSender messageSender) {
        synchronized (DeveloperConnectionClient.class) {
            if (sInstance != null) {
                Trace.debug(TAG, "connectToServer; instance already exists");
            } else {
                BootConfig bootconfig = BootConfig.createInstance(context);
                if (bootconfig == null) {
                    Trace.warning(TAG, "Bootconfig is null");
                } else {
                    sInstance = new DeveloperConnectionClient(URI.create(bootconfig.getDeveloperConnectionProxyUrl()), context, frameworkState, messageSender);
                }
            }
        }
    }

    public static void disconnectFromServer() {
        DeveloperConnectionManager.getHandler().post(new Runnable() {
            public void run() {
                DeveloperConnectionClient.disconnectFromServerInternal();
            }
        });
    }

    private static synchronized void disconnectFromServerInternal() {
        synchronized (DeveloperConnectionClient.class) {
            if (sInstance == null) {
                Trace.debug(TAG, "disconnectFromServer; instance is null");
            } else {
                sInstance.closeConnection();
            }
        }
    }

    public static void ping(final Context context, final FrameworkState frameworkState, final IDeviceMessageSender messageSender) {
        DeveloperConnectionManager.getHandler().post(new Runnable() {
            public void run() {
                DeveloperConnectionClient.pingInternal(context, frameworkState, messageSender);
            }
        });
    }

    private static synchronized void pingInternal(Context context, FrameworkState frameworkState, IDeviceMessageSender messageSender) {
        synchronized (DeveloperConnectionClient.class) {
            if (sInstance == null) {
                Trace.debug(TAG, "ping; instance is null: connecting...");
                connectToServer(context, frameworkState, messageSender);
            } else {
                sInstance.resetTimeout();
            }
        }
    }

    private DeveloperConnectionClient(URI uri, Context context, FrameworkState frameworkState, IDeviceMessageSender messageSender) {
        if (context == null) {
            throw new IllegalArgumentException("'context' cannot be null!");
        } else if (uri == null) {
            throw new IllegalArgumentException("'uri' cannot be null!");
        } else if (frameworkState == null) {
            throw new IllegalArgumentException("'frameworkState' cannot be null!");
        } else if (messageSender == null) {
            throw new IllegalArgumentException("'messageSender' cannot be null!");
        } else {
            this.mContext = new WeakReference(context);
            this.mFrameworkState = frameworkState;
            this.mWaitingForHandshake = true;
            this.mSessionManager = PblSessionManager.createInstance(context);
            this.mMessageSender = messageSender;
            this.mCallbackHandler = new Handler(Looper.getMainLooper());
            Trace.debug(TAG, "Connecting to " + uri);
            AsyncHttpClient.getDefaultInstance().websocket(uri.toString(), "my-protocol", new WebSocketConnectCallback() {
                public void onCompleted(Exception ex, WebSocket webSocket) {
                    if (ex != null) {
                        Trace.info(DeveloperConnectionClient.TAG, "Error connecting to proxy", ex);
                        return;
                    }
                    DeveloperConnectionClient.this.mWebSocket = webSocket;
                    DeveloperConnectionClient.this.mWebSocket.setStringCallback(DeveloperConnectionClient.this);
                    DeveloperConnectionClient.this.mWebSocket.setDataCallback(DeveloperConnectionClient.this);
                    DeveloperConnectionClient.this.mWebSocket.setClosedCallback(DeveloperConnectionClient.this);
                    DeveloperConnectionClient.this.mWebSocket.setEndCallback(DeveloperConnectionClient.this);
                    DeveloperConnectionClient.this.mCallbackHandler.post(new Runnable() {
                        public void run() {
                            DeveloperConnectionClient.this.onOpen();
                        }
                    });
                }
            });
        }
    }

    private synchronized void resetTimeout() {
        Trace.debug(TAG, "Starting timeout...");
        this.mTimeoutHandler.removeCallbacks(this.mTimeoutRunnable);
        this.mTimeoutHandler.postDelayed(this.mTimeoutRunnable, 600000);
    }

    public void sendMessageToAllConnections(final ByteBuffer bb) {
        DeveloperConnectionManager.getHandler().post(new Runnable() {
            public void run() {
                DeveloperConnectionClient.this.sendMessageToAllConnectionsInternal(bb);
            }
        });
    }

    private void sendMessageToAllConnectionsInternal(ByteBuffer bb) {
        synchronized (DeveloperConnectionClient.class) {
            if (sInstance != null) {
                Trace.debug(TAG, "sendMessageToAllConnections bytes: " + ByteUtils.hexDump(bb.array()));
                this.mWebSocket.send(bb.array());
            }
        }
    }

    private void onOpen() {
        Trace.debug(TAG, "onOpen");
        String token = this.mSessionManager.peekAuthToken();
        if (token == null) {
            Trace.warning(TAG, "token is null");
            return;
        }
        resetTimeout();
        ByteBuffer buffer = ByteBuffer.allocate(token.length() + 2);
        buffer.put((byte) 9);
        buffer.put((byte) token.length());
        buffer.put(token.getBytes());
        Trace.debug(TAG, "Sending auth");
        this.mWebSocket.send(buffer.array());
    }

    public void onStringAvailable(String s) {
        Trace.debug(TAG, "onMessage (string): '" + s + "'");
    }

    public void onDataAvailable(DataEmitter emitter, ByteBufferList bb) {
        final List<ByteBuffer> byteBuffers = new ArrayList();
        for (ByteBuffer message : bb.getAllArray()) {
            byteBuffers.add(message);
        }
        bb.recycle();
        this.mCallbackHandler.post(new Runnable() {
            public void run() {
                for (ByteBuffer message : byteBuffers) {
                    Trace.debug(DeveloperConnectionClient.TAG, "onMessage (bytes) size = " + message.capacity() + " mWaitingForHandshake = " + DeveloperConnectionClient.this.mWaitingForHandshake + " bytes: " + ByteUtils.hexDump(message.array()));
                    DeveloperConnectionClient.this.resetTimeout();
                    if (!DeveloperConnectionClient.this.mWaitingForHandshake) {
                        message.mark();
                        if (message.get() == WebSocketCommand.PROXY_CONNECTION_STATUS.getCode()) {
                            byte status = message.get();
                            if (status == (byte) -1) {
                                Trace.debug(DeveloperConnectionClient.TAG, "PROXY_CONNECTED");
                                DeveloperConnectionClient.this.mFrameworkState.setDeveloperConnectionProxyConnected(true);
                                if (DeveloperConnectionClient.this.mManager != null) {
                                    DeveloperConnectionClient.this.mManager.deInit();
                                }
                                Context context = (Context) DeveloperConnectionClient.this.mContext.get();
                                if (context != null) {
                                    DeveloperConnectionClient.this.mManager = new DeveloperConnectionManager(context, DeveloperConnectionClient.this, DeveloperConnectionClient.this.mMessageSender);
                                } else {
                                    Trace.debug(DeveloperConnectionClient.TAG, "context is null");
                                }
                            } else if (status == (byte) 0) {
                                Trace.debug(DeveloperConnectionClient.TAG, "PROXY_DISCONNECTED");
                                if (DeveloperConnectionClient.this.mManager != null) {
                                    DeveloperConnectionClient.this.mManager.deInit();
                                    DeveloperConnectionClient.this.mManager = null;
                                }
                            } else {
                                Trace.warning(DeveloperConnectionClient.TAG, "Unknown proxy status: " + status);
                            }
                        } else {
                            message.reset();
                            if (DeveloperConnectionClient.this.mManager != null) {
                                DeveloperConnectionClient.this.mManager.handleMessageFromPblSdk(message, DeveloperConnectionClient.this.mFrameworkState);
                            }
                        }
                    } else if (message.capacity() != 2) {
                        Trace.warning(DeveloperConnectionClient.TAG, "Received size = " + message.capacity() + " when expecting handshake response");
                        return;
                    } else if (message.get() != (byte) 9) {
                        Trace.warning(DeveloperConnectionClient.TAG, "expected auth key");
                        return;
                    } else {
                        byte response = message.get();
                        if (response == (byte) 0) {
                            Trace.debug(DeveloperConnectionClient.TAG, "AUTH_SUCCESS");
                            DeveloperConnectionClient.this.mWaitingForHandshake = false;
                            return;
                        }
                        if (response == (byte) 1) {
                            Trace.info(DeveloperConnectionClient.TAG, "AUTH_FAILURE");
                        } else {
                            Trace.warning(DeveloperConnectionClient.TAG, "Unknown response: " + response);
                        }
                        DeveloperConnectionClient.this.closeConnection();
                    }
                }
            }
        });
    }

    private void closeConnection() {
        Trace.debug(TAG, "closeConnection");
        if (this.mManager != null) {
            this.mManager.deInit();
            this.mManager = null;
        }
        if (this.mWebSocket != null) {
            this.mWebSocket.close();
        }
        this.mTimeoutHandler.removeCallbacks(this.mTimeoutRunnable);
        this.mFrameworkState.setDeveloperConnectionProxyConnected(false);
        sInstance = null;
    }

    public synchronized void onCompleted(final Exception ex) {
        this.mCallbackHandler.post(new Runnable() {
            public void run() {
                Trace.debug(DeveloperConnectionClient.TAG, "onClose", ex);
                DeveloperConnectionClient.this.closeConnection();
            }
        });
    }
}
